#pragma once

#include "user_contract.h"
#include <vex/core/contract/memory_resource.h>
#include <vex/core/make_raw_handle.h>
#include <vex/core/allocate_raw_handle.h>

#include <boost/intrusive_ptr.hpp>

#if defined(VEX_TESTS_ACTIVE) && defined(_DEBUG)
#include <strstream>
#include <CppUnitTest.h>
#endif

namespace tests {
    struct user_base_traits {
        template <class T> struct id;
        template <> struct id<int> : public std::integral_constant<int, 0> {};
        template <> struct id<double> : public std::integral_constant<int, 1> {};
        template <> struct id<std::string> : public std::integral_constant<int, 2> {};

        template <class TValue, class TStorage>
        static TValue const & get(TStorage const & p_storage)
        {
            return std::get<user_base_traits::id<TValue>::value>(p_storage);
        }

        template <class TValue, class TStorage>
        static TValue & get(TStorage & p_storage)
        {
            return std::get<id<TValue>::value>(p_storage);
        }
    };

    class VEX_ABSTRACT user_base : public user_contract {
        std::tuple<int, double, std::string> m_storage;
    protected:
        user_base() : m_storage(get_defaults()) {}
        user_base(int p_i) : m_storage(std::make_tuple(p_i, -1.0, "empty")) {}
        user_base(int p_i, double p_d) : m_storage(std::make_tuple(p_i, p_d, "empty")) {}
        user_base(int p_i, double p_d, char * const p_s) : m_storage(std::make_tuple(p_i, p_d, p_s)) {}
        user_base(int p_i, double p_d, std::string const & p_s) : m_storage(std::make_tuple(p_i, p_d, p_s)) {}
        user_base(int p_i, double p_d, std::string && p_s) : m_storage(std::make_tuple(p_i, p_d, std::move(p_s))) {}
        user_base(std::tuple<int, double, std::string> p_args)
            : m_storage(std::move(p_args))
        {}
        user_base(user_base && p_other)
            : m_storage(std::move(p_other.m_storage))
        {
#if defined(VEX_TESTS_ACTIVE) && defined(_DEBUG)
            std::stringstream t_out;
            t_out << __FUNCTION__/*typeid(*this).name() << " move constructor called*/ << std::endl;
            Microsoft::VisualStudio::CppUnitTestFramework::Logger::WriteMessage(t_out.str().c_str());
#endif
        }

        ~user_base()
        {
#if defined(VEX_TESTS_ACTIVE) && defined(_DEBUG)
            std::stringstream t_out;
            t_out << __FUNCTION__ /*typeid(*this).name() << "destructor called"*/ << std::endl;
            Microsoft::VisualStudio::CppUnitTestFramework::Logger::WriteMessage(t_out.str().c_str());
#endif
        }
    public:
        static std::tuple<int, double, std::string> get_defaults()
        {
            return std::make_tuple(-1, -1.0, "empty");
        }

        std::tuple<int, double, std::string> const& get_storage() const
        {
            return m_storage;
        }
    public:
        template <class TValue>
        TValue const & get() const
        {
            return user_base_traits::get<TValue>(m_storage);
        }

        template <class TValue>
        TValue & get()
        {
            return user_base_traits::get<TValue>(m_storage);
        }

        virtual int get_value(alias<int> const &) const 
        {
            return get<int>();
        }
        virtual double get_value(alias<double> const &) const 
        {
            return get<double>();
        }
        virtual char const * get_value(alias<char const *> const &) const 
        {
            return get<std::string>().c_str();
        }

        virtual void set_value(int p)
        {
            get<int>() = p;
        }
        virtual void set_value(double p)
        {
            get<double>() = p;
        }
        virtual void set_value(char const * p)
        {
            if (p) {
                get<std::string>().assign(p);
            }
        }

        virtual user_contract* clone(vex::core::contract::memory_resource* p_mem_res = nullptr) const
        {
            if (p_mem_res) {
                return vex::core::make_raw_handle<user_base>(this->get_storage());
            } else {
                return vex::core::allocate_raw_handle<user_base>(p_mem_res, this->get_storage());
            }
        }
    };

    class VEX_ABSTRACT user_base_mro 
        : public user_base
        , public vex::core::contract::memory_resource_owner
    {
        boost::intrusive_ptr<vex::core::contract::memory_resource> m_mem_res;
    protected:
        // obligatory constructor:
        user_base_mro(vex::core::contract::memory_resource* p_mem_res) 
            : user_base()
            , m_mem_res(p_mem_res)
        {
        }
        user_base_mro(vex::core::contract::memory_resource* p_mem_res,
            int p_i) 
            : user_base(p_i)
            , m_mem_res(p_mem_res) {}
        user_base_mro(vex::core::contract::memory_resource* p_mem_res,
            int p_i, double p_d) 
            : user_base(p_i, p_d)
            , m_mem_res(p_mem_res) {}
        user_base_mro(vex::core::contract::memory_resource* p_mem_res,
            int p_i, double p_d, char * const p_s) 
            : user_base(p_i, p_d, p_s) 
            , m_mem_res(p_mem_res) {}
        user_base_mro(vex::core::contract::memory_resource* p_mem_res,
            int p_i, double p_d, std::string const & p_s) 
            : user_base(p_i, p_d, p_s)
            , m_mem_res(p_mem_res) {}
        user_base_mro(vex::core::contract::memory_resource* p_mem_res,
            int p_i, double p_d, std::string && p_s) 
            : user_base(p_i, p_d, std::move(p_s))
            , m_mem_res(p_mem_res) {}
        user_base_mro(vex::core::contract::memory_resource* p_mem_res,
            std::tuple<int, double, std::string> p_args)
            : user_base(std::move(p_args))
            , m_mem_res(p_mem_res) {}
        user_base_mro(user_base_mro && p_other)
            : user_base(std::move(p_other))
            , m_mem_res(std::move(p_other.m_mem_res))
        {
#if defined(VEX_TESTS_ACTIVE) && defined(_DEBUG)
            std::stringstream t_out;
            t_out << __FUNCTION__ << std::endl;
            Microsoft::VisualStudio::CppUnitTestFramework::Logger::WriteMessage(t_out.str().c_str());
#endif
        }

        ~user_base_mro()
        {
#if defined(VEX_TESTS_ACTIVE) && defined(_DEBUG)
            std::stringstream t_out;
            t_out << __FUNCTION__ << std::endl;
            Microsoft::VisualStudio::CppUnitTestFramework::Logger::WriteMessage(t_out.str().c_str());
#endif
        }

    public:
        virtual user_contract* clone(vex::core::contract::memory_resource* p_mem_res = nullptr) const
        {
            if (p_mem_res) {
                return vex::core::allocate_raw_handle<user_base>(
                    p_mem_res, 
                    this->get_storage());
            } else {
                return vex::core::allocate_raw_handle<user_base>(
                    this->get_memory_resource(),
                    this->get_storage());
            }
        }
    };
}